/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package phd.dupenois.changeidentifiers.engine;

import java.util.logging.Level;
import java.util.logging.Logger;
import phd.dupenois.changeidentifiers.ChangeIdentifierSet;
import phd.dupenois.changeidentifiers.dotpatterns.DotPattern;
import phd.dupenois.changeidentifiers.dotpatterns.DotPatternBuffer;
import phd.dupenois.changeidentifiers.engine.testing.Test;
import phd.dupenois.changeidentifiers.engine.testing.TestManager;
import phd.dupenois.changeidentifiers.engine.testing.TestingInitialisationException;
import phd.dupenois.changeidentifiers.engine.testing.TestingRecordException;
import phd.dupenois.changeidentifiers.engine.testing.TimeStep;
import phd.dupenois.changeidentifiers.footprints.Footprint;
import phd.dupenois.changeidentifiers.footprints.algorithms.FootprintAlgorithm;

/**
 *
 * @author Max
 */
public class TerminatingRunEngine implements EngineInterface{
    private ChangeIdentifierSet changeIdentifierSet;
    private ChangeIdentifierApplicationInterface application;
    private EngineListener listener;
    private FootprintAlgorithm footprintAlgorithm;
//    private DotPatternBuffer dotPatternBuffer;
//    private Footprint currentFootprint;
//    private DotPattern previousChangeCausingDotPattern;
//    private Test test;
    private int pauseTime;
//    private String testTitle;
//    private String testDescription;
//    private long engineStartTime;
//    private boolean first;
    private boolean running;

    public TerminatingRunEngine(ChangeIdentifierApplicationInterface application, EngineListener listener) {
        this.application = application;
        this.listener = listener;
    }


    public void initialise(FootprintAlgorithm footprintAlgorithm,
            ChangeIdentifierSet changeIdentifierSet) {
        initialise(footprintAlgorithm, changeIdentifierSet, 0);
    }

    public void initialise(FootprintAlgorithm footprintAlgorithm,
            ChangeIdentifierSet changeIdentifierSet, int pauseTime) {
        this.changeIdentifierSet = changeIdentifierSet;
        this.footprintAlgorithm = footprintAlgorithm;
        this.footprintAlgorithm.initialise();
        this.pauseTime = pauseTime;
    }

    public Test runEngine(FootprintAlgorithm footprintAlgorithm,
            ChangeIdentifierSet changeIdentifierSet, DotPattern[] patterns,
            String testTitle, String testDescription){
        return runEngine(footprintAlgorithm, changeIdentifierSet, patterns,
                testTitle,testDescription, 0);
    }
    public Test runEngine(FootprintAlgorithm footprintAlgorithm,
            ChangeIdentifierSet changeIdentifierSet, DotPattern[] patterns,
            String testTitle, String testDescription,
            int pauseTime){
        Test test = new Test();
        test.setTitle(testTitle);
        test.setDescription(testDescription);
        DotPatternBuffer dotPatternBuffer= new DotPatternBuffer();
        dotPatternBuffer.initialiseTesting();
        this.initialise(footprintAlgorithm, changeIdentifierSet, pauseTime);
        for(DotPattern pattern: patterns){
            pattern.finalise();
            dotPatternBuffer.add(pattern);
        }
        //Bypass the threading
        this.run(test, dotPatternBuffer);
        
        return test;
    }

    /*
     * Runs without change identifiers to get a baseline time taken
     */
    public double getBaselineTimeTaken(FootprintAlgorithm footprintAlgorithm, DotPattern[] patterns){
        Test test = new Test();
        test.setDescription("Null test");
        test.setTitle("Null test");
        test.setFootprintAlgorithmClass(footprintAlgorithm.getClass().getCanonicalName());
        this.initialise(footprintAlgorithm, new ChangeIdentifierSet(), 0);
        DotPatternBuffer dotPatternBuffer= new DotPatternBuffer();
        for(DotPattern pattern: patterns){
            pattern.finalise();
            dotPatternBuffer.add(pattern);
        }
//        DotPatternBuffer cloneBuffer = this.dotPatternBuffer.clone();
        long startTime = System.currentTimeMillis();
        boolean first = true;
        ExecutionResult execution = null;
        while (dotPatternBuffer.hasNext()) {
            execution = runningCore(false, dotPatternBuffer.next(), first, test, execution);
            first = false;
        }
        long timeTaken = System.currentTimeMillis()-startTime;
        dotPatternBuffer.reset();
//        this.dotPatternBuffer = cloneBuffer;
        return timeTaken;
    }

    public void run(Test test, DotPatternBuffer dotPatternBuffer) {
        this.running = true;
        test.setFootprintAlgorithmClass(this.footprintAlgorithm.getClass().getCanonicalName());
        test.setChangeIdentifierSet(changeIdentifierSet);
     
        boolean first = true;
        test.setRedrawCount(0);
        ExecutionResult execution = null;
        while (dotPatternBuffer.hasNext()) {
            execution = runningCore(true, dotPatternBuffer.next(), first, test, execution);
            first = false;
        }
        long timeTaken = System.currentTimeMillis() - test.getStart();
        this.listener.finishedWithChangeIdentifiers(timeTaken, test.getRedrawCount());

        test.setTimeTaken(timeTaken);

        dotPatternBuffer.switchBuffers();
        test.setRedrawCountWithoutChangeIdentifiers(0);
        long testStartTime = System.currentTimeMillis();
        this.listener.startedRunningTestsWithoutChangeIdentifiers(testStartTime);
        first = true;
        execution = null;
        while (dotPatternBuffer.hasNext()) {
            execution = runningCore(false, dotPatternBuffer.next(), first, test, execution);
            first = false;
        }
        long testTimeTaken = System.currentTimeMillis() - testStartTime;
        test.setStartWithoutChangeIdentifiers(testStartTime);
        test.setTimeTakenWithoutChangeIdentifiers(testTimeTaken);
        this.listener.finishedRunningTestsWithoutChangeIdentifiers(testTimeTaken,
                test.getRedrawCountWithoutChangeIdentifiers(),
                testTimeTaken-timeTaken, test.getRedrawCount()-test.getRedrawCountWithoutChangeIdentifiers());
        this.listener.finishedEngine();
        this.running = false;
    }



    private ExecutionResult runningCore(boolean withChangeIdentifiers,
            DotPattern dotPattern, boolean first, Test test, ExecutionResult lastExecution){
        ExecutionResult result = null;
        try {
            if(first && withChangeIdentifiers){
                long engineStartTime = System.currentTimeMillis();
                test.setStart(engineStartTime);
                this.listener.startedEngine(engineStartTime);
            }
            
            this.application.drawDotPattern(dotPattern);

            result = this.execute(dotPattern, withChangeIdentifiers, first, test, lastExecution);

            if (this.pauseTime > 0) {
                try {
                    Thread.sleep(this.pauseTime);
                } catch (InterruptedException ex) {
                }
            }
        } catch (RuntimeException ex) {
            this.application.exceptionThrown(ex);
            ex.printStackTrace();
        } catch (Exception ex) {
            this.application.exceptionThrown(ex);
            ex.printStackTrace();
        }
        return result;
    }

    public void loadDotPattern(DotPattern dotPattern) {
    }

    public void run() {
    }

    private class ExecutionResult{
        private Footprint currentFootprint;
        private DotPattern previousChangeCausingDotPattern;
        public ExecutionResult(Footprint currentFootprint, DotPattern previousChangeCausingDotPattern){
            this.currentFootprint = currentFootprint;
            this.previousChangeCausingDotPattern = previousChangeCausingDotPattern;
        }
    }

    private ExecutionResult execute(DotPattern pattern, boolean withChangeIdentifiers, 
            boolean first, Test test, ExecutionResult lastExecution) throws Exception {
        long startTime = System.currentTimeMillis();
        TimeStep timeStep = null;
        timeStep = new TimeStep(startTime, pattern);
        this.listener.startedExecution(startTime);
        boolean redraw = false;
        ExecutionResult thisExecution;
        if(!first && withChangeIdentifiers){
//            long changeIdentifierStart = System.currentTimeMillis();
            redraw = this.changeIdentifierSet.redraw(lastExecution.currentFootprint, lastExecution.previousChangeCausingDotPattern, pattern);
//            System.out.println("+ Takes "+(System.currentTimeMillis()-changeIdentifierStart)+"ms to check cis");
            timeStep.setChangeIdentifierSetSnapshot(this.changeIdentifierSet.getSnapshot());
        }else{
            redraw = true;
        }
        if (redraw) {
           timeStep.setRedraw(true);
            if(withChangeIdentifiers){
                test.setRedrawCount(test.getRedrawCount()+1);
            }else{
                test.setRedrawCountWithoutChangeIdentifiers(test.getRedrawCountWithoutChangeIdentifiers()+1);
            }
            DotPattern previousChangeCausingDotPattern = pattern;
            long algTime = System.currentTimeMillis();
            this.listener.startedFootprintAlgorithm(this.footprintAlgorithm, algTime);
            Footprint currentFootprint = this.footprintAlgorithm.generateFootprint(pattern);
            long algTimeTaken = System.currentTimeMillis() - algTime;
            thisExecution = new ExecutionResult(currentFootprint, previousChangeCausingDotPattern);
//            System.out.println((withChangeIdentifiers?"+ With ":"- Without ")+"Takes "+(algTimeTaken)+"ms to run algorithm");
            this.listener.finishedFootprintAlgorithm(this.footprintAlgorithm, algTimeTaken);
//            if(!withChangeIdentifiers){
//                try{
//                    Thread.sleep(100);
//                }catch(InterruptedException ex){}
//            }
            this.application.drawFootprint(currentFootprint);
           
            timeStep.setAlgStarted(algTime);
            timeStep.setAlgTimeTaken(algTimeTaken);
            
        }else{
            thisExecution = lastExecution;
        }
        timeStep.setFootprint(thisExecution.currentFootprint);
        long timeTaken = System.currentTimeMillis() - startTime;
        this.listener.finishedExecution(timeTaken);
        timeStep.setTimeTaken(timeTaken);
        if(withChangeIdentifiers){
            test.addTimeStep(timeStep);
        }else{
            test.addTimeStepWithoutChangeIdentifiers(timeStep);
        }
        return thisExecution;
    }

    public void setupTestManager(TestManager testManager, String testTitle, String testDescription) throws TestingInitialisationException {
    }

    public boolean isRunning() {
        return this.running;
    }

    public void stop() {
    }






}
